package de.bk.timecalc.gui;

import static de.bk.timecalc.gui.GuiToolbox.*;

import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Spinner;
import org.jscience.mathematics.number.Rational;

import de.bk.timecalc.core.Item;
import de.bk.timecalc.core.ProbabilisticDistribution;
import de.bk.timecalc.core.TimerDirection;
import de.bk.timecalc.gui.GuiToolbox.DefaultSelectionListener;

class SettingsWindow
{
  private static final int OPTIONS_SHELL_WIDTH = 300;
  private static final int OPTIONS_SHELL_HEIGHT = 500;
  private static final int OPTIONS_ROW_HEIGHT = 20;
  private static final int OPTIONS_CHECKBOX_COLUMN_WIDTH = 10;
  private static final int OPTIONS_LABEL_COLUMN_WIDTH = 85;

  private static final int OPTIONS_VALUE_THIRD_COLUMN_WIDTH = 130;
  private static final int OPTIONS_VALUE_FOURTH_COLUMN_WIDTH = 20;
  private static final int OPTIONS_VALUE_COLUMN_WIDTH = OPTIONS_VALUE_THIRD_COLUMN_WIDTH
      + OPTIONS_VALUE_FOURTH_COLUMN_WIDTH;

  private Map<Item, Button> itemToCheckboxMap;
  private Map<Item, Slider> itemToSliderMap;
  private Map<Item, Spinner> itemToSpinnerMap;
  private Map<Button, Item> checkboxToItemMap;
  private Map<Slider, Item> sliderToItemMap;
  private Map<Spinner, Item> spinnerToItemMap;

  private GameWindowBehaviour gameWindowBehaviour;
  private SettingsModel settingsModel;

  private GridData gridDataLabelFirstAndSecondColumn;
  private GridData gridDataCheckboxFirstColumn;
  private GridData gridDataLabelSecondColumn;
  private GridData gridDataValueThirdAndFourthColumn;
  private GridData gridDataValueThirdColumn;
  private GridData gridDataValueFourthColumn;
  private GridData gridDataAllFourColumns;
  private GridData gridDataSeparator;

  private Shell shell;
  private Button radioButtonTimerDirectionUp;
  private Button radioButtonTimerDirectionDown;
  private Button checkboxRoundToFiveSeconds;
  private Button checkboxIgnoreMinute;
  private Button checkboxAutoNormalize;
  private boolean autoNormalize;

  SettingsWindow(GameWindowBehaviour gameWindowBehaviour)
  {
    this.gameWindowBehaviour = gameWindowBehaviour;
    this.autoNormalize = true;
  }

  void init(Shell mainShell)
  {
    this.createGridData();
    this.createShell(mainShell);
    this.createRadioButtonGroupTimerDirection();
    this.createCheckboxRoundToFive();
    this.createCheckboxIgnoreMinute();
    createHorizontalSeparator(this.shell, this.gridDataSeparator);
    this.createSliderGroupItemDistribution();
    createHorizontalSeparator(this.shell, this.gridDataSeparator);
    this.createButtonsOkCancelReset();
  }

  private void createGridData()
  {
    this.gridDataLabelFirstAndSecondColumn = new GridData();
    this.gridDataLabelFirstAndSecondColumn.horizontalSpan = 2;
    this.gridDataLabelFirstAndSecondColumn.horizontalAlignment = SWT.LEFT;
    this.gridDataLabelFirstAndSecondColumn.verticalAlignment = SWT.CENTER;
    this.gridDataLabelFirstAndSecondColumn.widthHint = OPTIONS_CHECKBOX_COLUMN_WIDTH
        + OPTIONS_LABEL_COLUMN_WIDTH;
    this.gridDataLabelFirstAndSecondColumn.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataCheckboxFirstColumn = new GridData();
    this.gridDataCheckboxFirstColumn.horizontalAlignment = SWT.LEFT;
    this.gridDataCheckboxFirstColumn.verticalAlignment = SWT.CENTER;
    this.gridDataCheckboxFirstColumn.widthHint = OPTIONS_CHECKBOX_COLUMN_WIDTH;
    this.gridDataCheckboxFirstColumn.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataLabelSecondColumn = new GridData();
    this.gridDataLabelSecondColumn.horizontalAlignment = SWT.LEFT;
    this.gridDataLabelSecondColumn.verticalAlignment = SWT.CENTER;
    this.gridDataLabelSecondColumn.widthHint = OPTIONS_LABEL_COLUMN_WIDTH;
    this.gridDataLabelSecondColumn.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataValueThirdAndFourthColumn = new GridData();
    this.gridDataValueThirdAndFourthColumn.horizontalSpan = 2;
    this.gridDataValueThirdAndFourthColumn.horizontalAlignment = SWT.LEFT;
    this.gridDataValueThirdAndFourthColumn.verticalAlignment = SWT.CENTER;
    this.gridDataValueThirdAndFourthColumn.widthHint = OPTIONS_VALUE_COLUMN_WIDTH;
    this.gridDataValueThirdAndFourthColumn.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataValueThirdColumn = new GridData();
    this.gridDataValueThirdColumn.horizontalAlignment = SWT.LEFT;
    this.gridDataValueThirdColumn.verticalAlignment = SWT.CENTER;
    this.gridDataValueThirdColumn.widthHint = OPTIONS_VALUE_THIRD_COLUMN_WIDTH;
    this.gridDataValueThirdColumn.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataValueFourthColumn = new GridData();
    this.gridDataValueFourthColumn.horizontalAlignment = SWT.LEFT;
    this.gridDataValueFourthColumn.verticalAlignment = SWT.CENTER;
    this.gridDataValueFourthColumn.widthHint = OPTIONS_VALUE_FOURTH_COLUMN_WIDTH;
    this.gridDataValueFourthColumn.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataAllFourColumns = new GridData();
    this.gridDataAllFourColumns.horizontalAlignment = SWT.CENTER;
    this.gridDataAllFourColumns.verticalAlignment = SWT.CENTER;
    this.gridDataAllFourColumns.horizontalSpan = 4;
    this.gridDataAllFourColumns.widthHint = OPTIONS_SHELL_WIDTH - 30;
    this.gridDataAllFourColumns.heightHint = OPTIONS_ROW_HEIGHT;

    this.gridDataSeparator = new GridData();
    this.gridDataSeparator.horizontalSpan = 4;
    this.gridDataSeparator.horizontalAlignment = SWT.CENTER;
    this.gridDataSeparator.verticalAlignment = SWT.CENTER;
    this.gridDataSeparator.widthHint = OPTIONS_LABEL_COLUMN_WIDTH
        + OPTIONS_CHECKBOX_COLUMN_WIDTH + OPTIONS_VALUE_COLUMN_WIDTH;
    this.gridDataSeparator.heightHint = 1;
  }

  private void createShell(Shell mainShell)
  {
    this.shell = new Shell(mainShell, SWT.DIALOG_TRIM | SWT.TITLE);
    this.shell.setText("Options");
    this.shell.setSize(OPTIONS_SHELL_WIDTH, OPTIONS_SHELL_HEIGHT);
    this.shell.setImage(new Image(mainShell.getDisplay(), TimeCalcGui.class
        .getResourceAsStream(GameWindowLayout.SHELL_ICON_IMAGE)));
    GridLayout layoutShell = new GridLayout(4, false);
    this.shell.setLayout(layoutShell);
    this.shell.addListener(SWT.Close, new Listener()
    {
      @Override
      public void handleEvent(Event event)
      {
        SettingsWindow.this.cancel();
        event.doit = false;
      }
    });
  }

  private void createRadioButtonGroupTimerDirection()
  {
    Label label = createLabel(this.shell,
        this.gridDataLabelFirstAndSecondColumn, "Timer counts", SWT.LEFT);
    label.setToolTipText("Direction of timer, counting up or down");
    Composite compositeRadioButtons = new Composite(this.shell, SWT.NONE);
    compositeRadioButtons
        .setToolTipText("Direction of timer, counting up or down");
    compositeRadioButtons.setLayoutData(this.gridDataValueThirdAndFourthColumn);
    compositeRadioButtons.setLayout(new FillLayout());
    this.radioButtonTimerDirectionUp = new Button(compositeRadioButtons,
        SWT.RADIO);
    this.radioButtonTimerDirectionUp.setText("up");
    this.radioButtonTimerDirectionDown = new Button(compositeRadioButtons,
        SWT.RADIO);
    this.radioButtonTimerDirectionDown.setText("down");
  }

  private void createCheckboxRoundToFive()
  {
    this.checkboxRoundToFiveSeconds = createCheckbox(this.shell,
        this.gridDataAllFourColumns);
    this.checkboxRoundToFiveSeconds.setText("Round to five seconds");
    this.checkboxRoundToFiveSeconds
        .setToolTipText("Only issue challenges like x:00, x:05, x:10, x:15, ...");
    this.checkboxRoundToFiveSeconds
        .addSelectionListener(new DefaultSelectionListener()
        {
          @Override
          public void widgetSelected(SelectionEvent e)
          {
            boolean selected = ((Button) e.getSource()).getSelection();
            SettingsWindow.this.settingsModel.setRoundToFiveSeconds(selected);
          }
        });
  }

  private void createCheckboxIgnoreMinute()
  {
    this.checkboxIgnoreMinute = createCheckbox(this.shell,
        this.gridDataAllFourColumns);
    this.checkboxIgnoreMinute.setText("Ignore minute");
    this.checkboxIgnoreMinute
        .setToolTipText("When checked, you will only need to enter the seconds, not the minute");
    this.checkboxIgnoreMinute
        .addSelectionListener(new DefaultSelectionListener()
        {
          @Override
          public void widgetSelected(SelectionEvent e)
          {
            boolean selected = ((Button) e.getSource()).getSelection();
            SettingsWindow.this.settingsModel.setIgnoreMinute(selected);
          }
        });
  }

  private void createSliderGroupItemDistribution()
  {
    this.itemToCheckboxMap = new HashMap<Item, Button>();
    this.itemToSliderMap = new HashMap<Item, Slider>();
    this.itemToSpinnerMap = new HashMap<Item, Spinner>();
    this.checkboxToItemMap = new HashMap<Button, Item>();
    this.sliderToItemMap = new HashMap<Slider, Item>();
    this.spinnerToItemMap = new HashMap<Spinner, Item>();

    Label label = createLabel(this.shell, this.gridDataAllFourColumns,
        "Item frequencies", SWT.LEFT);
    String tooltip = "Use this sliders to adjust how often each item will be asked for";
    label.setToolTipText(tooltip);
    for (Item item : Item.values())
    {
      this.createCheckboxSliderAndSpinner(item, tooltip);
    }

    this.checkboxAutoNormalize = createCheckbox(this.shell,
        this.gridDataAllFourColumns);
    this.checkboxAutoNormalize.setText("Auto-normalize");
    this.checkboxAutoNormalize
        .addSelectionListener(new DefaultSelectionListener()
        {
          @Override
          public void widgetSelected(SelectionEvent e)
          {
            SettingsWindow.this.autoNormalize = !SettingsWindow.this.autoNormalize;
            if (SettingsWindow.this.autoNormalize)
            {
              SettingsWindow.this.settingsModel.getItemDistribution()
                  .normalize();
            }
            SettingsWindow.this.refreshFromModel();
          }
        });

    GridData gridDataNormalize = new GridData();
    gridDataNormalize.horizontalSpan = 4;
    gridDataNormalize.horizontalAlignment = SWT.CENTER;
    gridDataNormalize.verticalAlignment = SWT.CENTER;
    gridDataNormalize.widthHint = 70;
    Button buttonNormalize = createButton(this.shell, "Normalize",
        gridDataNormalize);
    buttonNormalize.addSelectionListener(new DefaultSelectionListener()
    {
      @Override
      public void widgetSelected(SelectionEvent e)
      {
        SettingsWindow.this.settingsModel.getItemDistribution().normalize();
        SettingsWindow.this.refreshFromModel();
      }
    });
  }

  private void createCheckboxSliderAndSpinner(Item item, String tooltip)
  {
    Button checkbox = createCheckbox(this.shell,
        this.gridDataCheckboxFirstColumn);
    this.itemToCheckboxMap.put(item, checkbox);
    this.checkboxToItemMap.put(checkbox, item);
    checkbox.addSelectionListener(new DefaultSelectionListener()
    {
      @Override
      public void widgetSelected(SelectionEvent e)
      {
        Button checkbox = (Button) e.getSource();
        Item item = SettingsWindow.this.checkboxToItemMap.get(checkbox);
        boolean selected = checkbox.getSelection();
        if (!selected)
        {
          SettingsWindow.this.settingsModel.getItemDistribution().remove(item);
          SettingsWindow.this.itemToSliderMap.get(item).setEnabled(false);
          SettingsWindow.this.itemToSpinnerMap.get(item).setEnabled(false);
        }
        else
        {
          ProbabilisticDistribution<Item> itemDistribution = SettingsWindow.this.settingsModel
              .getItemDistribution();
          if (!itemDistribution.contains(item))
          {
            itemDistribution.add(item, 0);
          }
          SettingsWindow.this.itemToSliderMap.get(item).setEnabled(true);
          SettingsWindow.this.itemToSpinnerMap.get(item).setEnabled(true);
        }
        if (SettingsWindow.this.autoNormalize)
        {
          SettingsWindow.this.settingsModel.getItemDistribution().normalize();
        }
        SettingsWindow.this.refreshFromModel();
      }
    });

    createLabel(this.shell, this.gridDataLabelSecondColumn, item.getName(),
        SWT.LEFT).setToolTipText(tooltip);

    Slider slider = new Slider(this.shell, SWT.HORIZONTAL);
    slider.setLayoutData(this.gridDataValueThirdColumn);
    slider.setToolTipText(tooltip);
    this.itemToSliderMap.put(item, slider);
    this.sliderToItemMap.put(slider, item);
    slider.addSelectionListener(new DefaultSelectionListener()
    {
      @Override
      public void widgetSelected(SelectionEvent e)
      {
        Slider slider = (Slider) e.getSource();
        int newValue = slider.getSelection();
        Item item = SettingsWindow.this.sliderToItemMap.get(slider);
        SettingsWindow.this.settingsModel.getItemDistribution()
            .changeProbability(item, new Rational(newValue, 100));
        if (SettingsWindow.this.autoNormalize)
        {
          SettingsWindow.this.settingsModel.getItemDistribution().normalize();
        }
        SettingsWindow.this.refreshFromModel();
      }
    });

    Spinner spinner = createSpinner(this.shell, this.gridDataValueFourthColumn);
    spinner.setToolTipText(tooltip);
    this.itemToSpinnerMap.put(item, spinner);
    this.spinnerToItemMap.put(spinner, item);
    spinner.addSelectionListener(new DefaultSelectionListener()
    {
      @Override
      public void widgetSelected(SelectionEvent e)
      {
        Spinner spinner = (Spinner) e.getSource();
        int newValue = spinner.getSelection();
        Item item = SettingsWindow.this.spinnerToItemMap.get(spinner);
        SettingsWindow.this.settingsModel.getItemDistribution()
            .changeProbability(item, new Rational(newValue, 100));
        if (SettingsWindow.this.autoNormalize)
        {
          SettingsWindow.this.settingsModel.getItemDistribution().normalize();
        }
        SettingsWindow.this.refreshFromModel();
      }
    });
  }

  private void createButtonsOkCancelReset()
  {
    Composite compositeOkCancelReset = new Composite(this.shell, SWT.NONE);
    compositeOkCancelReset.setLayoutData(this.gridDataAllFourColumns);
    compositeOkCancelReset.setLayout(new FillLayout(SWT.HORIZONTAL));
    createButton(compositeOkCancelReset, "OK").addSelectionListener(
        new DefaultSelectionListener()
        {
          @Override
          public void widgetSelected(SelectionEvent e)
          {
            SettingsWindow.this.onOk();
          }
        });
    createButton(compositeOkCancelReset, "Reset").addSelectionListener(
        new DefaultSelectionListener()
        {
          @Override
          public void widgetSelected(SelectionEvent e)
          {
            SettingsWindow.this.onReset();
          }
        });
    createButton(compositeOkCancelReset, "Cancel").addSelectionListener(
        new DefaultSelectionListener()
        {
          @Override
          public void widgetSelected(SelectionEvent e)
          {
            SettingsWindow.this.onCancel();
          }
        });
  }

  void show(SettingsModel settingsModel)
  {
    if (settingsModel == null)
    {
      throw new NullPointerException("settingsModel may not be null");
    }
    this.settingsModel = settingsModel;
    this.refreshFromModel();
    this.shell.open();
  }

  void hide()
  {
    this.shell.setVisible(false);
  }

  private void refreshFromModel()
  {
    switch (this.settingsModel.getTimerDirection())
    {
      case UP:
        this.radioButtonTimerDirectionDown.setSelection(false);
        this.radioButtonTimerDirectionUp.setSelection(true);
        break;
      case DOWN:
        this.radioButtonTimerDirectionDown.setSelection(true);
        this.radioButtonTimerDirectionUp.setSelection(false);
        break;
      default:
        throw new AssertionError();
    }

    ProbabilisticDistribution<Item> itemDistribution = this.settingsModel
        .getItemDistribution();
    for (Item item : Item.values())
    {
      this.setCheckboxSliderAndSpinnerValue(itemDistribution, item);
    }
    this.checkboxAutoNormalize.setSelection(this.autoNormalize);

    this.checkboxRoundToFiveSeconds.setSelection(this.settingsModel
        .isRoundToFiveSeconds());
  }

  private void setCheckboxSliderAndSpinnerValue(
      ProbabilisticDistribution<Item> itemDistribution, Item item)
  {
    Button checkbox = this.itemToCheckboxMap.get(item);
    Slider slider = this.itemToSliderMap.get(item);
    Spinner spinner = this.itemToSpinnerMap.get(item);
    Rational probability;
    try
    {
      probability = itemDistribution.getProbability(item);
      int probabilityTimesHundred = probability.times(100).intValue();
      checkbox.setSelection(true);
      slider.setValues(probabilityTimesHundred, 0, 100, 1, 1, 10);
      slider.setEnabled(true);
      spinner.setValues(probabilityTimesHundred, 0, 100, 2, 2, 10);
      spinner.setEnabled(true);
    }
    catch (NoSuchElementException e)
    {
      checkbox.setSelection(false);
      slider.setValues(0, 0, 100, 1, 1, 10);
      slider.setEnabled(false);
      spinner.setValues(0, 0, 100, 1, 1, 10);
      spinner.setEnabled(false);
    }
  }

  private void onOk()
  {
    boolean down = this.radioButtonTimerDirectionDown.getSelection();
    boolean up = this.radioButtonTimerDirectionUp.getSelection();
    if (up && down)
    {
      throw new AssertionError();
    }
    else if (!up && !down)
    {
      throw new AssertionError();
    }
    else if (up)
    {
      this.settingsModel.setTimerDirection(TimerDirection.UP);
    }
    else
    {
      this.settingsModel.setTimerDirection(TimerDirection.DOWN);
    }
    this.gameWindowBehaviour.transferSettings(this.settingsModel);
    this.settingsModel = null;
    this.hide();
  }

  private void onCancel()
  {
    this.cancel();
  }

  private void onReset()
  {
    this.settingsModel = new SettingsModel();
    this.refreshFromModel();
  }

  private void cancel()
  {
    this.settingsModel = null;
    this.hide();
  }
}
